/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package filecopier.gui.copy;

import java.io.File;
import java.io.IOException;
import java.util.Calendar;
import java.util.SimpleTimeZone;
import java.beans.PropertyChangeSupport;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import javax.swing.JOptionPane;
import javax.swing.JFileChooser;
import filecopier.Singleton;
import filecopier.script.Script;
import filecopier.script.ScriptEntry;
import filecopier.script.RestoreScriptEntry;
import filecopier.options.Options;
import filecopier.options.RenameFmtString;
import filecopier.gui.WorkerMessage;
import custom.util.FileCopyHelper;
import custom.util.FileCopyCancelException;
import org.apache.log4j.Logger;
import org.apache.log4j.Level;

/**
 *
 * @author david
 */
public class FileCopyGuiHelper implements FileCopyHelper, PropertyChangeListener {

    private boolean update = false;
    private boolean confirmOverwrite = false;
    private boolean useGMT = false;
    private RenameFmtString renameFmtString = null;
    private Options.RenameType renameType = null;
    private Script restoreScript = null;
    private Logger logger = null;
    private int restoreCount = 0;
    private int copiedCt = 0;
    private int attemptCt = 0;
    private int confirmOverwriteValue = CONFIRM_OVERWRITE_INIT_VALUE;
    private PropertyChangeSupport pcs = null;
    private volatile Boolean cancelRequest = false;
    private static final int CONFIRM_OVERWRITE_INIT_VALUE = -2;
    private static final int YES_OPTION = 0;
    private static final int YESTOALL_OPTION = 1;
    private static final int NO_OPTION = 2;
    private static final int NOTOALL_OPTION = 3;

    public FileCopyGuiHelper(final Script restoreScript) {
        init();
        this.restoreScript = restoreScript;
        pcs = new PropertyChangeSupport(this);
    }

    public void init() {
        Options options = Singleton.getInstance().getOptions();
        this.useGMT = options.getUseGMT();
        this.update = options.getUpdateOnly();
        this.renameType = options.getRenameType();
        this.confirmOverwrite = options.promptForOverwrite();
        this.renameFmtString = options.getRenameFmtString();
        this.logger = Singleton.getInstance().getFileGuiLogger();
    }

    public void addPropertyChangeListener(final String propertyName, final PropertyChangeListener pcl) {
        pcs.addPropertyChangeListener(propertyName, pcl);
    }

    public void handleException(final IOException e) {
        // Send the error message only to the gui logger and the error message plus the stack trace to the file logger.
        setWorkerMessage(new WorkerMessage(Singleton.getInstance().getGuiLogger(), Level.ERROR, e.getMessage()));
        setWorkerMessage(new WorkerMessage(Singleton.getInstance().getFileLogger(), Level.ERROR, "IOException", e));

        int oldCt = attemptCt;
        setAttemptCt(oldCt, ++attemptCt);
    }

    public boolean handleExistingFile(final File existingFile, final File sourceFile) throws FileCopyCancelException {
        boolean status = true;

        // If update is true, we are supposed to make sure that the destination file is older than the source file
        // before copying it. If the files are directories, however, I do not want to perform this check. The check
        // will be performed for each file in the directory.
        if ((update == true) && (!existingFile.isDirectory() && !sourceFile.isDirectory()) &&
                (existingFile.lastModified() >= sourceFile.lastModified())) {
            setWorkerMessage(new WorkerMessage(logger, Level.INFO, "\"" + existingFile + "\" is not older than " + sourceFile + "\". Skipping file."));
            status = false;
        } else {
            if (renameType == Options.RenameType.Skip) {
                // Rename type is skip, which means do not copy any existing files.
                setWorkerMessage(new WorkerMessage(logger, Level.INFO, "\"" + existingFile + "\" already exists. Skipping file."));
                status = false;
            } else if (renameType == Options.RenameType.Overwrite) {
                status = handleOverwrite(existingFile);
            } else if (renameType == Options.RenameType.RenameAuto) {
                status = handleRenameAuto(existingFile);
            } else {
                status = handleRenameManual(existingFile);
            }
        }

        return status;
    }

    private boolean handleOverwrite(final File existingFile) throws FileCopyCancelException {
        boolean status = true;

        if (confirmOverwrite) {
            // We have to confirm from the user before doing an overwrite, so send a property change notification to the Event Dispatch Thread.
            setConfirmOverwritePrompt(existingFile);
            // Loop until user responds to the prompt.
            while (confirmOverwriteValue == CONFIRM_OVERWRITE_INIT_VALUE) {
                // Sleep for 250 milliseconds.
                try {
                    Thread.sleep(250);
                } catch (InterruptedException ignore) {
                }
            }
            // Once we get here, user has responded, so process the response.
            if (confirmOverwriteValue == YES_OPTION) {
                status = true;
            } else if (confirmOverwriteValue == YESTOALL_OPTION) {
                // User said yes to all, so don't confirm subsequent overwrites.
                confirmOverwrite = false;
                status = true;
            } else if (confirmOverwriteValue == NO_OPTION || confirmOverwriteValue == JOptionPane.CLOSED_OPTION) {
                status = false;
            } else // User selected No To All
            {
                throw new FileCopyCancelException("Subsequent copy operations aborted");
            }
        }

        return status;
    }

    private boolean handleRenameAuto(final File existingFile) {
        boolean status = true;

        try {
            createNewFileWithLastModified(existingFile);
        } catch (IOException e) {
            status = false;
            handleException(e);
        }

        return status;
    }

    private void createNewFileWithLastModified(final File toFile) throws IOException {
        File newToFile = new File(toFile.getParent(), createNewName(toFile.getName(), toFile.lastModified()));
        int idx = 1;
        while (newToFile.exists()) {
            String newName = newToFile.getName();
            newName = incrementNewFileName(newName, idx++);
            newToFile = new File(newToFile.getParent(), newName);
        }

        if (!toFile.renameTo(newToFile)) {
            throw new IOException("File NOT renamed from \"" +
                    toFile + "\" to \"" + newToFile + "\"");
        }
        setWorkerMessage(new WorkerMessage(logger, Level.INFO, "File renamed from \"" + toFile + "\" to \"" + newToFile + "\""));
        //options.reportStatus(Level.FINE, "[SUCCESS]" + toFile + " renamed to " + newToFile);
        // Write info to the restore script file.
        if (restoreScript != null) {
            ScriptEntry rse = new RestoreScriptEntry(newToFile, toFile);
            restoreScript.addScriptEntry(rse);
            restoreCount++;
        }
    }

    private String createNewName(final String fileName, final long lastModMillis) {
        String nameOnly, newFileName, ext;
        // See if this file has an extension
        int idx = fileName.lastIndexOf('.');
        if (idx > 0) {
            // File has an extension, so put the name and extension in separate Strings.
            nameOnly = fileName.substring(0, idx);
            ext = fileName.substring(idx);
        } else {
            // File has no extension, so set sNameOnly to point to the whole name and set sExt to point to
            // an empty String.
            nameOnly = fileName;
            ext = "";
        }

        // Get the date that the to file was last modified and put that value in a Calendar object.
        Calendar cal = null;
        if (useGMT) {
            cal = Calendar.getInstance(new SimpleTimeZone(0, "GMT"));
        } else {
            cal = Calendar.getInstance();
        }
        cal.setTimeInMillis(lastModMillis);
        renameFmtString.validateRenameFmtString(cal);

        // Build the new file name with the formatted last modified date string appended to it.
        newFileName = nameOnly + renameFmtString.getPreviewRenameFmtString() + ext;

        return newFileName;
    }

    private String incrementNewFileName(String name, final int idx) {
        String newName = null;

        // See if this file has already been renamed.
        int firstBracket = name.lastIndexOf('[');
        if (firstBracket != -1) {
            int secondBracket = name.lastIndexOf(']');
            if ((secondBracket != -1) && ((name.length() == secondBracket + 1) || (name.charAt(secondBracket + 1) == '.'))) {
                newName = name.substring(0, firstBracket) + "[" + idx + name.substring(secondBracket);
                name = newName;
            }
        }

        if (newName == null) {
            // See if file name has an extension
            int extIdx = name.lastIndexOf('.');
            if (extIdx != -1) {
                String nameOnly = name.substring(0, extIdx);
                String extName = name.substring(extIdx);
                name = nameOnly + "[" + idx + "]" + extName;
            } else {
                name += "[" + idx + "]";
            }
        }

        return name;
    }

    private boolean handleRenameManual(final File existingFile) throws FileCopyCancelException {
        boolean status = true;
        try {
            status = promptForNewName(existingFile);
        } catch (IOException e) {
            status = false;
            handleException(e);
        }

        if (status == false) {
            setWorkerMessage(new WorkerMessage(logger, Level.ERROR, "Renaming of \"" + existingFile + "\" failed."));
            Object[] answer = {"Yes", "No"};
            int result = JOptionPane.showOptionDialog(Singleton.getInstance().getFrame(),
                    "Renaming of \"" + existingFile + "\" failed." +
                    "Do you want to abort all subsequent copy operations?",
                    "Rename Aborted", JOptionPane.YES_NO_OPTION,
                    JOptionPane.QUESTION_MESSAGE, null, answer, answer[0]);

            if (result == JOptionPane.YES_OPTION) {
                throw new FileCopyCancelException("Subsequent copy operations aborted");
            }
        }

        return status;
    }

    private boolean promptForNewName(final File toFile) throws IOException {
        boolean status = false;
        File newToFile = new File(toFile.getParent(), createNewName(toFile.getName(), toFile.lastModified()));
        int idx = 1;
        while (newToFile.exists() && idx <= Integer.MAX_VALUE) {
            String newName = newToFile.getName();
            newName = incrementNewFileName(newName, idx++);
            newToFile = new File(newToFile.getParent(), newName);
        }

        Singleton.getInstance().getFileAlwaysLogger().info("About to call fileChooser...");
        JFileChooser fileChooser = new JFileChooser();
        fileChooser.setApproveButtonMnemonic('R');
        fileChooser.setCurrentDirectory(new File(toFile.getParent()));
        fileChooser.setSelectedFile(newToFile);
        int rename = fileChooser.showOpenDialog(Singleton.getInstance().getFrame());
        if (JFileChooser.APPROVE_OPTION == rename) {
            if (!toFile.renameTo(newToFile)) {
                throw new IOException("File NOT renamed from \"" + toFile + "\" to \"" + newToFile + "\"");
            }
            setWorkerMessage(new WorkerMessage(logger, Level.INFO, "File renamed from \"" + toFile + "\" to \"" + newToFile + "\""));
            status = true;
        }

        // Write info to the restore script file.
        if (restoreScript != null) {
            ScriptEntry rse = new RestoreScriptEntry(newToFile, toFile);
            restoreScript.addScriptEntry(rse);
            restoreCount++;
        }

        return status;
    }

    public boolean handleFileInUse(final File existingFile) {
        boolean status = false;
        if (renameType == Options.RenameType.Overwrite) {
            setWorkerMessage(new WorkerMessage(logger, Level.INFO, "Overwrite failed because \"" + existingFile + "\" is in use by another process. Attempting rename."));
            status = handleRenameAuto(existingFile);
        }

        return status;
    }

    public void reportCopyStatus(final String msg, boolean status) throws FileCopyCancelException {
        if (status) {
            setWorkerMessage(new WorkerMessage(logger, Level.INFO, msg));
            ++copiedCt;
        } else {
            setWorkerMessage(new WorkerMessage(logger, Level.WARN, msg));
        }

        int oldCt = attemptCt;
        setAttemptCt(oldCt, ++attemptCt);

        if (cancelRequest == true) {
            throw new FileCopyCancelException("Copy Operations cancelled.");
        }
    }

    public int getRestoreCount() {
        return restoreCount;
    }

    private void setAttemptCt(int oldCt, int newCt) {
        pcs.firePropertyChange("attemptCt", oldCt, newCt);
    }

    private void setConfirmOverwritePrompt(final File existingFile) {
        pcs.firePropertyChange("confirmOverwritePrompt", false, existingFile);
    }

    private void setWorkerMessage(final WorkerMessage workerMessage) {
        pcs.firePropertyChange("workerMessage", null, workerMessage);
    }

    public int getCopiedCt() {
        return copiedCt;
    }

    public void propertyChange(PropertyChangeEvent pce) {
        String propertyName = pce.getPropertyName();
        if (propertyName.equals("cancel")) {
            synchronized (cancelRequest) {
                cancelRequest = true;
            }
        } else if (propertyName.equals("confirmOverwriteValue")) {
            confirmOverwriteValue = (Integer) pce.getNewValue();
        }
    }
}
